home *** CD-ROM | disk | FTP | other *** search
/ PCMania 64 / PCMania CD64_1.iso / phy / phy005 / fatmap / fatmap2.esp < prev    next >
Encoding:
Text File  |  1997-08-02  |  33.0 KB  |  823 lines

  1.   
  2.              Fast affine texture mapping II (fatmap2.txt)
  3.              --------------------------------------------
  4.   
  5.                                 por
  6.                                     
  7.                           Mats Byggmastar
  8.   
  9.                                 MRI
  10.   
  11.                      programador 3D en Doomsday
  12.                           mri@penti.sit.fi
  13.   
  14.                   17 Abril 1997, Jakobstad, Finland
  15.  
  16.       Traducido al castellano por Angel Iglesias AKA Matrix / Dosis
  17.  
  18.                       22 Mayo 1997, Lugo, España
  19.  
  20.  
  21.       Eres libre de enviar este documento a cualquier sitio que encuentres
  22.       apropiado que lo conserves sin ninguna modificación.
  23.       Esta es una información libre, no puedes cobrar nada por ella.
  24.       Las empresas deberán contactar conmigo si parte de este código es
  25.       usado como parte de un producto comercial.
  26.  
  27.   
  28.   
  29.   Tabla de contenidos
  30.   --------------------
  31.   
  32.   1.  Sobre este documento
  33.   2.  Aviso     
  34.   3.  Sobre el codigo fuente
  35.   4.  Poligonos convexos
  36.   5.  Dibujando poligonos convexos
  37.   6.  Gradientes de textura constantes y poligonos
  38.   7.  idiv e imul en coma fija 16:16
  39.   8.  Funcion ceil() de coma fijo
  40.   9.  Precision de subpixel
  41.   10. Precision de subtexel
  42.   11. Evitando divide overflow en calculos descendentes
  43.   12. Contadores en bucles internos
  44.   13. Bucle interno de 8:16 bit
  45.   14. Bucle interno con bitmaps de cualquier tamaño
  46.   15. Bucle interno de 8:15 bit para tiled (embaldosados)
  47.   16. Poligonos por segundo
  48.   17. Saludos  
  49.   
  50.   
  51.   
  52.   1.  Sobre este documento
  53.   -------------------------
  54.   
  55.       Este documento es la continuación a fatmap.txt publicado el 19 de
  56.       Junio de 1996. El objetivo de este segundo documento es hacer más
  57.       preciso el mapeador de texturas. He omitido los triángulos y me he
  58.       concentrado en los polígonos convexos para hacer el recorte (clipping)
  59.       más eficiente. El esquema del bucle interno de bloques descrito en
  60.       fatmap.txt, ahora ha sido realizado en un bucle interno de 6 ciclos
  61.       de reloj que se ha puesto en acción con buenos resultados.
  62.  
  63.       Como en el documento previo, los bucles internos en ensamblador
  64.       están desarroyados para el procesador Intel Pentium.
  65.  
  66.       La gente que esta buscando descripciones sobre los mapeadores de
  67.       texturas con corrección de perspectiva deberían visitar el Game
  68.       Developer Magazine en http://www.gdmag.com y encargar los
  69.       numeros con los artículos de Chris Hecker sobre este tema!.
  70.  
  71.       Yo, el autor, soy un viejo informático e ingeniero de telecomunicaciones
  72.       de 25 años (B.Sc.), actualmente trabajando como profesor en una
  73.       escuela vocacional para alumnos de 16 a 19 años estudiando ordenadores,
  74.       electrónica, automatización y energía eléctrica. Hago graficos 3D en
  75.       tiempo principalmente como un hobby, y soy un miembro activo del
  76.       grupo Finlandes de demos Doomsday. Mi sueño es un día trabajar todo
  77.       el tiempo con gráficos 3D.
  78.  
  79.       Gracias especialmente a Harriet Mattfolk por la prelectura de este
  80.       documento y ayudarme con la sintaxis inglesa.
  81.  
  82.  
  83.  
  84.   
  85.   2.  Aviso
  86.   ----------
  87.   
  88.       El autor no acepta ninguna responsabilidad, si algo en este documento
  89.       o los fuentes o ejecutables que lo compañan, producen alguna perdida
  90.       de datos o daños en tu equipo.
  91.  
  92.   
  93.   
  94.   
  95.   3.  Sobre el codigo fuente
  96.   ---------------------------
  97.   
  98.       Excepto por los bucles internos y otras funciones varias, el codigo
  99.       esta escrito en C++ muy cercano al C. Para realizar la mayoría del
  100.       codigo he hecho los bucles internos en ensamblador en línea.
  101.       Personalmente tengo las funciones principales de mapeado en un
  102.       ensamblador optimizado, pero sería poco logico incluir mucho de
  103.       estos fuentes en este tipo de documento.
  104.  
  105.       El codigo fuente que debería acompañar a este documento esta dividido
  106.       en 8 ficheros:
  107.  
  108.           misc.h      - Varias declaraciones de estructuras y funciones
  109.   
  110.           clip.cpp    - Recorte 2D de Sutherland-Hodgman
  111.           draw.cpp    - Calcula deltas, recortes y llama a los mapeadores
  112.           main.cpp    - Programa simple de test
  113.   
  114.           flat.cpp    - Rellenado flat 
  115.           gouraud.cpp - Rellenado gouraud 
  116.           txtmap.cpp  - Mapeador de texturas
  117.           txtarb.cpp  - Mapeador de texturas de cualquier tamaño
  118.           txttil.cpp  - Mapeador de texturas en bloques
  119.   
  120.       Los últimos cinco ficheros son los interesantes. Los otros ficheros
  121.       se han incluido para poder hacer el programa de text. Clip.cpp es
  122.       mi propia implementación del algoritmo Sutherland-Hodgman y debería ser
  123.       de algun interés. Puedes encontrar la teoría del algorimo Sutherland-
  124.       Hodgman en cualquier libro de textos sobre gráficos.
  125.  
  126.       De un tiempo a un tiempo, he visto gente buscar una manera rápida
  127.       de rellenar polígonos con un color sólido. Por lo tanto he incluido
  128.       el rellenado flat. Tu podrías reemplazar la llamada a memset() por
  129.       cualquier otro bucle interno. Recientemente he participado en un
  130.       debate en el grupo de noticias comp.graphics.algorithms concerniente
  131.       a coma flotante vs coma fija en un rellenado gouraud, por lo que he
  132.       decidido incluir muy versión de rellenado gouraud.
  133.  
  134.       El fuente debería ser compilado con Watcom C/C++ ya que el ensamblador
  135.       en linea es específico de Watcom. No ha sido probado con la versión
  136.       10.0 de Watcom C/C++. La versión compilada del programa de prueba
  137.       (main.exe) va incluida. Necesitarás el DOS4GW protected mode para
  138.       ejecutarlo.
  139.  
  140.   
  141.   
  142.   4.  Poligonos convexos
  143.   -----------------------
  144.   
  145.       Primero, definamos lo que es un poligono convexo. Un poligono convexo
  146.       no debe tener ninguno de sus esquinas apuntando al centro del poligono.
  147.       En otras palabras, el angulo entre dos lados formando un vertice no
  148.       deben ser mayores que 180 grados. Esto significa que los triangulos son
  149.       siempre poligonos convexos. El opuesto de convexo, es concavo y pueden
  150.       tener esquinas apuntando hacia adentro.
  151.                        ______         ______________     ____        ____
  152.             /\       /        \      \              /   |     \    /     |
  153.           /   \    /            \      \           /    |       \/       |
  154.         /      \   \            /      /          /     |                |
  155.       /_________\    \ ______ /      /__________ /      |________________|
  156.   
  157.         convexo        convexo         concavo               concavo
  158.   
  159.       Los mapeadores presentados en este documento solo pueden dibujar
  160.       poligonos con una excepcion. El tercer polígono de arriba puede
  161.       ser manejado apropiadamente aunque sea concavo. Esto es porque aunque
  162.       sea concavo ningun scanline debe ser dividido cuando se renderiza.
  163.       El cuarto polígono, no puede ser manejado apropiadamente porque
  164.       habría que dividir scanlines.
  165.   
  166.   
  167.   5.  Dibujar poligonos convexos
  168.   -------------------------------
  169.   
  170.       Dibujar poligonos convexos es tan simple y rapido como dibujar
  171.       triangulos. La unica diferencia es que el codigo del vertice explorado
  172.       debe estar en un bucle interno para poligonos. Si nosotros solo
  173.       renderizamos triangulos, sabemos que estos siempre tiene 3 vertices
  174.       lo cual hace el codigo un poco mas sencillo.
  175.   
  176.       El primer paso en la funcion de poligonos es localizar los vertices de
  177.       arriba hacia abajo. Entonces nosotros esploraremos la tabla de vertices
  178.       y buscaremos la y minima y maxima. Cojamos el siguiente poligono de
  179.       4 vertices como ejemplo:
  180.   
  181.                          v1
  182.                         / \
  183.                       /      \
  184.                     /           \
  185.                   /                \ 
  186.              v2 /                    / v0
  187.                 \                 /
  188.                  \             /  
  189.                   \         /
  190.                    \     /
  191.                     \ /
  192.                     v3
  193.   
  194.       Notar que los vertices deben estar ordenados en sentido antihorario.
  195.       Hemos encontrado que v1 es el vertices superior y v3 el inferior.
  196.   
  197.       Ahora sabemos que cuando exploremos el lado izquierdo del poligono,
  198.       deberíamos empezar en v1 y movernos hacia delante a traves de la tabla
  199.       hasta que llegasemos a v3. Generalmente, cuando avanzamos, deberiamos
  200.       volver al comienzo de la tabla si nos pasamos de ella. P.e. si
  201.       llegamos al ultimo vertice, el siguiente sera el v0.
  202.                          v1
  203.                         / 
  204.                       /
  205.                     /
  206.                   /
  207.              v2 /
  208.                 \
  209.                  \
  210.                   \
  211.                    \
  212.                     \
  213.                     v3
  214.   
  215.       Para el lado derecho empezamos en v1 y retrocedemos a traves de la
  216.       tabla hasta que lleguemos a v3.. Generalmente cuando retrocedemos,
  217.       deberíamos volver al final de la tabla sin nos pasamos. P.e. cuando
  218.       llegamos a v0 nuestro proximo vertice será el ultimo en la tabla.
  219.   
  220.                          v1
  221.                           \ 
  222.                              \
  223.                                 \
  224.                                    \ 
  225.                                      / v0
  226.                                   /
  227.                                /  
  228.                             /
  229.                          /
  230.                       /
  231.                     v3
  232.   
  233.       En nuestro ejemplo tenemos 2 secciones en el lado izquierdo:  
  234.   
  235.         v1 - v2   y  v2 - v3
  236.   
  237.       y 2 secciones en el derecho:  
  238.   
  239.         v1 - v0   y  v0 - v3
  240.   
  241.       El segundo paso en la funcion del poligono, es calcular el decremento de
  242.       la coordenada x para la primera sección en el lado derecho. Si la
  243.       sección tiene de altura 0, prueba la siguiente hasta que encuentres
  244.       una que no sea 0.
  245.   
  246.       El tercer paso es calcular el decremento de la coordenada x y cualquier
  247.       otro decremento (P.e. las coordenadas de textura u y v) para la primera
  248.       sección en el lado izquierdo. Si la sección tiene de altura 0, prueba
  249.       la siguiente hasta que encuentres una que no sea 0.
  250.   
  251.       Si todas las secciones de un lado tienen altura 0, el poligono
  252.       tendrá una altura 0 y no deberá ser dibujado.
  253.   
  254.       Ahora podemos empezar a interpolar los valores a lo largo de los lados
  255.       izquierdo y derecho tal como dibujamos cada scanline usando un
  256.       bucle interno.
  257.   
  258.                          v1
  259.                         /-\
  260.                       /------\
  261.                     /-----------\
  262.                   /                \ 
  263.              v2 /                     v0
  264.   
  265.       Cuando llegamos al final de la seccion, buscaremos la siguiente
  266.       sección con altura diferente de 0 y calcularemos el nuevo decremento.
  267.       Si todas las secciones del poligono estan listas, p.e. si hemos
  268.       llegado al vertice inferior, el poligono esta acabado.
  269.   
  270.  
  271.  
  272.   6.  Gradientes de textura constantes y poligonos
  273.   -------------------------------------------------
  274.  
  275.       Con triangulos los gradientes de textura (las coordenadas de textura u
  276.       y v se decrementan a lo largo de la superficie del triangulo) esta
  277.       garantizado que son constantes. Entonces podemos calcular los
  278.       gradientes (dudx, dvdx) una vez y usar los mismos valores para
  279.       todos los scanlines del triangulo.
  280.         
  281.       Si empezamos con un triangulo y lo recortamos en un poligono, los
  282.       gradientes de la textura permanecen constantes sobre toda la
  283.       superficie. Entonces si calculamos los gradientes antes de recortar
  284.       el triángulo podremos seguir usando el metodo de gradiente de textura
  285.       constante para los poligonos.
  286.   
  287.       Calcular los gradientes de la textura para un triangulo puede hacerse
  288.       de la siguiente manera:
  289.                             v0
  290.                             /\ 
  291.                           /   \ 
  292.                         /      \ 
  293.                       /_________\ 
  294.                     v1           v2
  295.   
  296.           double d = (v0.x - v2.x) * (v1.y - v2.y) -
  297.                      (v1.x - v2.x) * (v0.y - v2.y);
  298.           if(d == 0.0) return;
  299.           double id = 1.0/d * 65536.0;
  300.           long dudx = ((v0.u - v2.u) * (v1.y - v2.y) -
  301.                        (v1.u - v2.u) * (v0.y - v2.y)) * id;
  302.           long dvdx = ((v0.v - v2.v) * (v1.y - v2.y) -
  303.                        (v1.v - v2.v) * (v0.y - v2.y)) * id;
  304.   
  305.       Los datos de los vertices x,y,u,v se asume que estan en coma flotante
  306.       y el resultado dudx, dvdx esta en coma fija 16:16.
  307.   
  308.   
  309.   
  310.   7.  idiv e imul en coma fija 16:16
  311.   -----------------------------------
  312.   
  313.       Todos los mapeadores trabajan con coma fija 16:16 internamente. No
  314.       explicare como trabaja la coma fija internamente, puede verser en
  315.       cualquier sitio. Hare notar que necesitamos realizar las divisiones
  316.       y multiplicaciones en ensamblador antes que en C. Entonces desde
  317.       aqui puedes asumir que el codigo como:
  318.   
  319.           long dxdy = (width << 16) / height;
  320.           long x = v1.x + ((prestep * dxdy) >> 16);
  321.   
  322.       Debe realizarse con algunas funciones de ensamblador en linea:  
  323.   
  324.           long dxdy = idiv16(width, height);
  325.           long x = v1.x + imul16(prestep, dxdy);
  326.   
  327.   
  328.   
  329.   8.  Funcion ceil() de coma fija
  330.   --------------------------------
  331.   
  332.       Necesitamos una funcion ceil() para la precision de subpixel y subtexel.
  333.       Definicion de ceil() es:
  334.   
  335.           ceil(x) devuelve el menor entero no < x
  336.   
  337.   
  338.       e.g.  ceil(1.0)  devuelve  1
  339.             ceil(1.5)  devuelve  2
  340.             ceil(-2.0) devuelve -2
  341.             ceil(-1.5) devuelve -1
  342.   
  343.       Si limitamos x solo a numeros positivos podemos hacer una version de
  344.       ceil() en coma fija 16:16 muy facilmente. Sumaremos 0xffff a x y
  345.       rotaremos hacia la derecha por 16.
  346.   
  347.           inline long ceil(long x)
  348.           {
  349.               x += 0xffff;
  350.               return x >> 16;
  351.           }
  352.   
  353.       Nota que esta funcion no da el resultado correcto si x es negativa.
  354.       Esto no es problema ya que x nunca sera negativa de la manera en
  355.       que ceil() se usa en los mapeadores.
  356.       
  357.       
  358.   
  359.   9.  Precision de subpixel
  360.   --------------------------
  361.  
  362.       La primera cosa a notar cuando pretendemos dibujar un poligono con
  363.       precision de subpixel es que nunca debemos reducir las coordenadas
  364.       de pantalla a integers. Hoy en dia es comun usar la coma flotante
  365.       para todo en un motor 3D y convertir las coordenadas de pantalla
  366.       proyectadas a integer justo antes de entrar en la funcion de poligonos.
  367.       Esta conversion, por supuesto, deberia ser de float a coma fija con
  368.       lo que no perderiamos la parte fraccionaria. La parte fraccionaria es
  369.       de hecho la posicion del subpixel que afectara a como seran
  370.       renderizados los incrementos en los lados del polígono.
  371.   
  372.       Sin la precisión de supixels los polígonos saltarán un pixel de golpe
  373.       cuando el objeto se mueva lentamente sobre la pantalla. Con precision
  374.       de subpixel los pixeles que hacen los lados entre polígonos flotaran
  375.       lentamente, haciendo que los bordes parezcan moverse lentamente
  376.       por pantalla.
  377.  
  378.       Calculando el decremento de una sección de precisión de subpixel:  
  379.   
  380.           long scanlines = ceil(v2.y) - ceil(v1.y);
  381.           if(scanlines <= 0) return;              // Sección con altura 0
  382.           
  383.           long height = v2.y - v1.y;              
  384.           long dxdy = ((v2.x - v1.x) << 16) / altura; 
  385.       
  386.           long prestep = (ceil(v1.y) << 16) - v1.y;
  387.           long left_x = v1.x + ((prestep * dxdy) >> 16);   
  388.   
  389.       La altura de la sección, o el número de scalineas, serán un integer.
  390.       Cuando calculamos el decremento (dxdy) no usaremos esa altura, es
  391.       preferible usar la altura real que incluye la parte fraccionaria.
  392.       Aplicamos la precisión de subpixel al decremento ajustando la coordenada
  393.       x inicial por el total que ha sido quitado cuando seleccionamos la
  394.       coordenada y superior usando ceil();
  395.  
  396.       Interpolar a lo largo de las secciones de precisión de subpixel
  397.       izquierda y derecha:
  398.   
  399.           for(  )
  400.           {
  401.               long x1 = ceil(left_x);               // Empieza scanline x
  402.               long width = ceil(right_x) - x1;      // Ancho del scanline
  403.           
  404.               if(width > 0)
  405.               {
  406.                   Ahora dibuja el scanline desde x1,y, con width pixels
  407.                   de ancho.
  408.               }
  409.                 
  410.               left_x  += left_dxdy;
  411.               right_x += right_dxdy;
  412.               y++;
  413.           }
  414.   
  415.   
  416.   
  417.   10. Precisión de subtexel
  418.   --------------------------
  419.  
  420.       Calcular los decrementos de u,v (dudy,dvdy) a lo largo de la sección
  421.       de la misma manera que el decremento de la coordenada x y preparar
  422.       los valores iniciales de la misma forma:
  423.   
  424.           long height = v2.y - v1.y;              
  425.           long dudy = ((v2.u - v1.u) << 16) / height; 
  426.           long dvdy = ((v2.v - v1.v) << 16) / height; 
  427.       
  428.           long prestep = (ceil(v1.y) << 16) - v1.y;
  429.           long left_u = v1.u + ((prestep * dudy) >> 16);   
  430.           long left_v = v1.v + ((prestep * dvdy) >> 16);   
  431.   
  432.       Cuando interpolamos con precisión de subtexel debemos prepara u,v
  433.       antes de dibujar cada scanline. Esto es porque redondeamos el
  434.       comienzo del scanline (x1) hasta el punto más cercano usando ceil();
  435.         
  436.           for(  )
  437.           {
  438.               long x1 = ceil(left_x);                 // Empieza scanline x
  439.               long width = ceil(right_x) - x1;        // Ancho del scanline
  440.       
  441.               if(width > 0)
  442.               {
  443.                   long prestep = (x1 << 16) - left_x;
  444.                   long u = left_u + ((prestep * dudx) >> 16); 
  445.                   long v = left_v + ((prestep * dvdx) >> 16);
  446.                   
  447.                   Ahora dibujamos el escanline desde x1,y,u,v, con width
  448.                   pixels de ancho.
  449.               }
  450.       
  451.               left_x  += left_dxdy;
  452.               left_u  += left_dudy;
  453.               left_v  += left_dvdy;
  454.               right_x += right_dxdy;
  455.               y++;
  456.           }
  457.   
  458.   
  459.   
  460.   11. Evitando divide overflow en calculo de decrementos
  461.   -------------------------------------------------------
  462.       
  463.       Para algunas secciones que son muy delgadas, el calculo del decremento
  464.       puede producir un overflow.
  465.   
  466.       Mira el caso siguiente:  
  467.   
  468.           v1.x = 0000:0000    v2.x = 0002:0000
  469.           v1.y = 0000:0000    v2.y = 0000:0001
  470.       
  471.       ceil(v2.y) - ceil(v1.y) devolverán que esto es un scanline excepto
  472.       si la altura actual es justo la fracción de un pixel.
  473.   
  474.           height = v2.y - v1.y = 0000:00001
  475.           width  = v2.x - v1.y = 0002:00000
  476.   
  477.       entonces realizando (width << 16) / heigh) causará un divide overflow.
  478.       Estamos pretendiendo hacer un mapeado perfecto por lo que no podemos
  479.       cojer un incremento por defecto para el decremento. Una manera de
  480.       evitar el overflow es multiplicar el ancho (width) por la inversa
  481.       de la altura (heigh) usando solo una precisión 18:14.
  482.  
  483.           long height = v2.y - v1.y;
  484.           long inv_height = (0x10000 << 14) / height;
  485.           long dxdy = ((v2.x - v1.x) * inv_height) >> 14;
  486.   
  487.       Notase que este método solo puede ser usado para este caso especial
  488.       donde la altura de la sección es menor que un pixel. Otras secciones
  489.       que son mayores que un pixel deberían calcularse normalmente.
  490.       
  491.   
  492.   
  493.   12. Contadores de bucles en bucles internos
  494.   --------------------------------------------
  495.   
  496.       Existe una forma ingenionsa de combinar el movimiento de un puntero
  497.       destino y un contador de bucle en bucles interno. Puede no salvar mucho
  498.       (medio ciclo de reloj en un Pentium) pero puedes encontrar otras formas
  499.       de usarlo.
  500.  
  501.       Asume que queremos dibujar una línea horizontal en la pantalla con
  502.       los siguientes datos:
  503.   
  504.           al  = color
  505.           ecx = ancho de la linea
  506.           edi = puntero destino al primer pixel de la izquierda
  507.   
  508.       La forma normal sería:  
  509.   
  510.           inner:
  511.               mov   [edi], al         ; dibuja
  512.               inc   edi               ; incrementa el puntero destino
  513.               dec   ecx               ; decrementa el contador de bucle
  514.               jnz   inner
  515.   
  516.       Otra forma es combinar el puntero destino y el ancho y dibujar la linea
  517.       desde la derecha hacia la izquierda.
  518.   
  519.           inner:
  520.               mov   [edi+ecx-1], al   
  521.               dec   ecx               
  522.               jnz   inner
  523.  
  524.       En el bucle superior nos deshacemos de una instrucción. Sin embargo,
  525.       estamos escribiendo en memoria desde una dirección alta a una baja.
  526.       Esto es malo para los buffers de escritura. Deberíamos incrementar
  527.       las direcciones en vez de decrementarlas. Esto puede hacerse de
  528.       esta manera:
  529.   
  530.               lea   edi, [edi+ecx]  
  531.               neg   ecx               ; el contador va de -ancho a 0
  532.   
  533.           inner:
  534.               mov   [edi+ecx], al     
  535.               inc   ecx               
  536.               jnz   inner
  537.       
  538.       El destinatario es movido primera al final de la línea y el contador
  539.       es negado. El primera pixel se dibujara en comienzo+ancho-ancho, o lo
  540.       que es lo mismo, al comienzo de la linea.
  541.   
  542.               neg   ecx  
  543.       
  544.       puede ser reemplazado por:
  545.   
  546.               xor   ecx, -1
  547.               inc   ecx
  548.   
  549.       El cual muchas veces puede ser emparejado con otras instrucciones en
  550.       la situación del código. neg no es emparejable, 1 ciclo de reloj.
  551.       Deberíamos acabar con:
  552.   
  553.               lea   edi, [edi+ecx]  
  554.               xor   ecx, -1
  555.               inc   ecx
  556.           inner:
  557.               mov   [edi+ecx], al     
  558.               inc   ecx               
  559.               jnz   inner
  560.   
  561.   
  562.   
  563.   13. Bucle interno de 8:16 bit
  564.   ------------------------------
  565.   
  566.       Despues de que fatmap.txt fuera publicado mandé un muy bonito bucle
  567.       interno 8:16 de 4 ciclos de reloj hecho por Russel Simmons (Armitage
  568.       /Beyond). En este original documento los scanlines son dibujados de
  569.       derecha a izquierda. Estube una temporada en ello y presento aqui
  570.       la nueva versión que dibuja los scanlines de izquierda a derecha.
  571.  
  572.           ; bitmap (256x256) debe estar alineado con 64k. (16 bits bajos = 0)
  573.           ; eax =  u frac           : -
  574.           ; ebx =  bitmap ptr       : v int      : u int
  575.           ; ecx =  scanline width
  576.           ; edx =  v frac           : v int step : u int step
  577.           ; esi =  u frac step      : 0          : 0
  578.           ; edi =  destination ptr
  579.           ; ebp =  v frac step      : 0          : 0
  580.   
  581.           lea   edi, [edi+ecx]
  582.           xor   ecx, -1
  583.           inc   ecx
  584.   
  585.        inner:
  586.           mov   al, [ebx]      ; obtener color
  587.           add   edx, ebp       ; v frac += v frac step
  588.           adc   bh, dh         ; v int  += v int step (+carry de v frac)
  589.           add   eax, esi       ; u frac += u frac step
  590.           adc   bl, dl         ; u int  += u int step (+carry de u frac)
  591.           mov   [edi+ecx], al  ; dibujar pixel
  592.           inc   ecx
  593.           jnz   inner
  594.   
  595.   
  596.       Esto es un buen bucle interno con la suficiente precisión, el cual
  597.       puede encargarse de deformaciones de textura. Notese que la textura
  598.       debe estar alineada sobre 64 Kb.
  599.  
  600.       Una forma de alinear los mapas de texturas sobre 64 Kb es primero
  601.       obetener un buffer con un largo mayor en 64 Kb que el mapa de texturas
  602.       y despues alinear el puntero dentro del buffer.
  603.  
  604.           char source[256*256];       // bitmap origen
  605.           char bigbuffer[256*256*2];  // 2*64k byte buffer
  606.           
  607.           char * aligned = (char *) (((int)(bigbuffer + 0xffff)) & ~0xffff); 
  608.           memcpy(aligned, source, 256*256);
  609.   
  610.       Ahora se accede al bitmap usando el puntero alineado.
  611.   
  612.   
  613.   
  614.   14. Bucles internos con bitmaps de cualquier tamaño
  615.   ----------------------------------------------------
  616.   
  617.       La idea para el siguiente bucle interno de 5 ciclos de reloj, fué
  618.       obtenida de la subdivisión de scanlines en ek mapeador de texturas de
  619.       Chris Hecker. Este bucle no se fia en el hecho de que las texturas
  620.       tengan siempre 256x256 pixels. Usamos una tabla de 2 dword en vez
  621.       de realizar la multiplicación de v por el ancho. Por lo tanto puede
  622.       trabajar con mapas de cualquier tamaño. La parte fraccionaria en
  623.       este bucle puede ser de hasta 32 bits aunque solo usemos 16 bits aqui.
  624.       Notese que el bitmap no tiene que estar alineado.
  625.  
  626.       El truco en este bucle es convertir el flag de carry desde la parte
  627.       fraccionaria de la suma de v en un índice en la tabla. Entonces
  628.       obtiene el incremento de la textura desde la tabla.
  629.  
  630.           ; el bitmap puede tener cualquier tamaño
  631.           ; calcula la tabla duvdxstep de acuerdo con el ancho
  632.           ; dvdxfrac =  v frac step : 0
  633.           ; eax =  u frac           : 0
  634.           ; ebx =  v frac           : 0
  635.           ; ecx =  scanline width
  636.           ; edx =  u frac step      : 0
  637.           ; esi =  bitmap ptr
  638.           ; edi =  destination ptr
  639.   
  640.           lea   edi, [edi+ecx]
  641.           xor   ecx, -1
  642.           add   ebx, [dvdxfrac]               ; v frac += v frac step
  643.           inc   ecx
  644.           sbb   ebp, ebp                      ; -1 si carry desde add
  645.   
  646.        inner:
  647.           add   eax, edx                      ; u frac += u frac step
  648.           mov   bl, [esi]                     ; obtiene el color
  649.           adc   esi, [duvdxstep+4+ebp*4]      ; mueve el puntero a la textura
  650.           add   ebx, [dvdxfrac]               ; v frac += v frac step
  651.           sbb   ebp, ebp                      ; -1 si carry desde add
  652.           mov   [edi+ecx], bl                 ; dibuja el pixel
  653.           inc   ecx
  654.           jnz   inner
  655.       
  656.   
  657.       La tabla dudvxstep puede hacerse de la siguiente manera:  
  658.   
  659.           long duvdxstep[2];
  660.   
  661.           duvdxstep[0] = (dudx >> 16) + (dvdx >> 16) * anchomapa + anchomapa;
  662.           duvdxstep[1] = (dudx >> 16) + (dvdx >> 16) * anchomapa;  
  663.   
  664.       Esto significa que cuando obtener el carry de la suma de v y ebp
  665.       se vuelve -1, direccionaremos dudvxstep[0] y sumaremos una linea
  666.       extra en el bitmap.
  667.  
  668.       Un inconveniente de este bucle interno es que no puede realizar
  669.       deformaciones de textura. Es decir, nunca puedes situar las coordenadas
  670.       u, v fuera del mapa de la textura o leera datos fuera del mapa
  671.       de textura o causara una protection fault.
  672.  
  673.  
  674.   
  675.   15. Bucle interno de bloques en 8:15
  676.   -------------------------------------
  677.   
  678.       En fatmap.txt describía un método para colocar el mapa de textura
  679.       como bloques de 8x8 para un uso más eficiente del caché. Entonces no
  680.       conocía ninguna forma de hacer el bucle de cambio de bit en menos de
  681.       11 ciclos de reloj. Pero un día se me ocurrió y escribí inmediatamente
  682.       una versión de 8 ciclos de reloj en 8:16. Esta versión en teoría no
  683.       funcionaba pero un comienzo. El bucle interno inferior fue desarrollado
  684.       a partir de la versión original de 8 ciclos en un periodo de 2 meses.
  685.       Fue desarrollado solo en un papel la primera vez que probaba cualquiera
  686.       de los bucles, era esta versión y funcionó perfectamente.
  687.  
  688.       La versión inferior funciona en 6 ciclos de reloj y usa una
  689.       interpolación de 8:15 bits. Aqui los bloques de 8x8 son usados puero
  690.       cualquier tipo de esquema puede usarse, solo modificando las mascaras
  691.       de los bits. El bitmap de 256x256 no necesita estar alineado, pero
  692.       para que el esquema de bloques sea efectivo el bitmap debería estar
  693.       alineado sobre 32 bytes. Permite la deformación de texturas.
  694.  
  695.           ; el bitmap (256x256) debe estar almacenado en bloques de 8x8
  696.           ; tildudx =  wwwww11111111www1fffffffffffffffb  (w=whole, f=frac)
  697.           ; tildvdx =  11111wwwwwwww1111fffffffffffffffb
  698.           ; eax =  u   wwwww00000000www0fffffffffffffffb
  699.           ; ebx =  v   00000wwwwwwww0000fffffffffffffffb
  700.           ; ecx =  ancho del scanline
  701.           ; edi =  puntero destino
  702.           ; esi =  puntero al bitmap
  703.   
  704.           lea   edi, [edi+ecx-1]
  705.           xor   ecx, -1
  706.           lea   ebp, [eax+ebx]                            ; u+v
  707.           inc   ecx
  708.   
  709.        inner:
  710.           add   eax, [tildudx]                            ; u += tildudx
  711.           add   ebx, [tildvdx]                            ; v += tildvdx
  712.           shr   ebp, 16                                   ; (u+v) >> 16
  713.           and   eax, 11111000000001110111111111111111b    ; borra huecos
  714.           and   ebx, 00000111111110000111111111111111b
  715.           inc   ecx
  716.           mov   dl, [esi+ebp]                             ; obtiene el color
  717.           lea   ebp, [eax+ebx]                            ; u+v
  718.           mov   [edi+ecx], dl                             ; dibuja el pixel
  719.           jnz   inner
  720.       
  721.   
  722.       Convertir dudx, dvdx en coma fija 16:16 al formato de boques
  723.       puede hacerse de la siguiente manera:
  724.  
  725.           tildudx = (((dudx << 8) & 0xf8000000) +
  726.                      ((dudx >> 1) & 0x00007fff) +
  727.                       (dudx       & 0x00070000)) | 0x07f88000;
  728.       
  729.           tildvdx = (((dvdx << 3) & 0x07f80000) +
  730.                      ((dvdx >> 1) & 0x00007fff)) | 0xf8078000;
  731.   
  732.       Notese que rellenamos los huecos en los bits con 1. Esto es porque
  733.       debemos forzar a los bits a saltar sobre los huecos cuando sumemos
  734.       en el bucle interno. Esos unos se borrarán despues de la suma. Hacemos
  735.       esto con las instrucciones and en el bucle interno.
  736.  
  737.       Antes de entrar en el bucle interno debemos convertir u,v al
  738.       formato de bloques:
  739.  
  740.           u = ((u << 8) & 0xf8000000) +
  741.               ((u >> 1) & 0x00007fff) +
  742.                (u       & 0x00070000);
  743.       
  744.           v = ((v << 3) & 0x07f80000) +
  745.               ((v >> 1) & 0x00007fff);
  746.   
  747.       Debería notarse que solo 15 bits son usados para la parte fraccionaria
  748.       y que es rotada hacia la derecha un bit. Esto es asi porque no debemos
  749.       dejar que la parte fraccionaria produzca un overflow y llene el
  750.       puntero a la textura cuando sumemos u+v usando la instucción lea.
  751.       Hemos añadido un huevo entre la parte fraccionaria y la parte principal
  752.       para evitar eso.
  753.  
  754.       El propio bitmap puede ser "ablocado" de la siguiente manera:  
  755.  
  756.           char source[256*256];   // bitmap lineal de origen
  757.           char tiled[256*256];    
  758.   
  759.           for(int v=0; v<256; v++) {
  760.               for(int u=0; u<256; u++) {
  761.                   int dst = ((u<<8) & 0xf800)+(u & 0x0007)+((v<<3) & 0x07f8);
  762.                   tiled[dst] = source[u+v*256];
  763.               }
  764.           }
  765.   
  766.   
  767.   
  768.   16. Poligonos por segundo
  769.   --------------------------
  770.   
  771.       De un tiempo a un tiempo he visto coders hablando sobre la velocidad
  772.       de sus motores 3D especificando cuandos polígonos por segundo puede
  773.       dibujar, esto es completamente irrelevante desde que no especifican
  774.       bajo que condiciones han hecho ese test. La velocidad puede depender
  775.       muchi del tamaño y orientación de los polígonos tanto como la forma
  776.       en que los poligonos son texturados. Otros factores puedes contribuir
  777.       a la velocidad, como el tipo de administrador de memoria o el
  778.       extensor de DOS.
  779.  
  780.       Con el simple programa de test que acompaña a este documento intendo
  781.       comparar la velocidad entre los diferentes mapeadores. En este caso
  782.       la comparación es válida porque trabajan exactamente bajo las mismas
  783.       condiciones. Consigo los siguientes resultados en mi Pentium 120:
  784.  
  785.           Flat                        3494
  786.           Gouraud                     1849
  787.           Texturas 256x256            1214
  788.           Cualquier tamaño en textura 1196
  789.           Texturas por bloques        1520
  790.   
  791.       Esas figuras no son muy impresionantes, pero recordar que los
  792.       triángulos pueden tener cualquier largo como lo permita x,y en el
  793.       rango [0...320],[0...200]. En cualquier caso deberías notar que el
  794.       rellenado flat (usando el standard memset() en el bucle interno) es
  795.       como un par de veces más rápido que el resto. El mapeador con textura
  796.       de tamaño variable y el mapeador normal deberían ejecutarse a la
  797.       misma velocidad. El mapeador por bloques aparece como el ganador
  798.       porque permitimos a los valores u,v variar a lo largo de la superficie
  799.       del triángulo, es decir, que los capitulos sobre cachés se volveran
  800.       mas importantes que una precálculo o un bucle interno rápido.
  801.  
  802.   
  803.  
  804.  
  805.   17. Saludos
  806.   ------------
  807.   
  808.       Members de Doomsday
  809.       Members de SoCS
  810.       Members de Esteem
  811.       Phil Carmody
  812.       Nix / The Black Lotus
  813.       Submissive / Cubic Team & $eeN
  814.       Armitage / Beyond
  815.       Jare / Iguana
  816.       Wog / Orange
  817.       #coders. Ninguno mencionado, ninguno olvidado.
  818.       A todos mis estudiantes en YiJ 1996-1997; Data3, AD2, Elm2, El1A, El1B
  819.   
  820.       ...mi corazon en Oslo
  821.   
  822.   .fdf (eof :).
  823.